package fr.ants.model.ai.behaviour;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Stack;

import fr.ants.model.Ant;
import fr.ants.model.Grid;
import fr.ants.model.Node;
import fr.ants.model.Resource;
import fr.ants.model.ai.pathfinding.IPathFinder;
import fr.ants.utils.MathUtils;

/**
 * <p>Behavior of a Queen Ant.</p>
 *
 * @author Vianney DUPOY DE GUITARD
 */
public class WorkerBehavior extends AntBehavior {

	/** Steps on explore mode. */
	private static final int EXPLORE_STEPS = 3;

	/** The last direction the worker took. */
	private Direction previousDirection;

	/** The node where the ant was previously. */
	private Node previousNode;

	/** Creates a new worker behavior. */
	public WorkerBehavior() {
		setAntState(AntState.EXPLORING);
	}

	/** {@inheritDoc} */
	public void live(Ant ant, Grid grid) {
		switch (getAntState()) {
		case EXPLORING:
			explore(ant, grid);
			break;
		case FOLLOW:
			follow(ant, grid);
			break;
		case CROP:
			crop(ant);
			break;
		case WALKING_NEST:
			walkToNest(ant, grid);
			break;
		case DEPOSE:
			ant.getNest().addResource(ant.releaseFood());
			setAntState(AntState.EXPLORING);
			break;
		}
	}

	/** {@inheritDoc} */
	public void reset() { }

	/**
	 * Actions triggered when the ant is exploring.
	 * @param ant : the ant exploring.
	 */
	private void explore(Ant ant, Grid grid) {
		Node node = ant.getNode();

		// Looks if there any resource close to the ant.
		Node nodeWithResource = findClosestResource(grid, node);
		boolean foodNear = nodeWithResource != null && isResourceInteresting(ant, nodeWithResource.getResource());
		boolean foodFound = isResourceInteresting(ant, ant.getNode().getResource());
		boolean pheroFound = node.getPheromoneLevel() > 0;

		if (foodFound) {
			// The resource is on the node where the ant is.
			setAntState(AntState.CROP);
			ant.getPath().clear();			
		} else if (pheroFound) {
			// The ant found pheromones and has to follow it.
			setAntState(AntState.FOLLOW);
			ant.getPath().clear();
			previousDirection = null;
		} else if (foodNear) {
			// A resource is close and the ant has to move to another node.
			ant.getPath().clear();
			grid.moveAnt(node, nodeWithResource, ant);
		} else {
			// Creates a path if the ant can't walk anymore.
			while (ant.getPath().isEmpty() || !ant.getPath().peek().isWalkable()) {
				Direction d = findDirection();
				previousDirection = d;
				ant.getPath().clear();
				switch (d) {
				case NORTH:
					// Let's go north.
					for (int i = EXPLORE_STEPS; i > 0; i--) {
						ant.getPath().add(grid.getNode(node.getX(), node.getY() - i));
					}
					break;
				case SOUTH:
					// Let's go south.
					for (int i = EXPLORE_STEPS; i > 0; i--) {
						ant.getPath().add(grid.getNode(node.getX(), node.getY() + i));
					}
					break;
				case EAST:
					// Let's go east.
					for (int i = EXPLORE_STEPS; i > 0; i--) {
						ant.getPath().add(grid.getNode(node.getX() + i, node.getY()));
					}
					break;
				case WEST:
					// Let's go west.
					for (int i = EXPLORE_STEPS; i > 0; i--) {
						ant.getPath().add(grid.getNode(node.getX() - i, node.getY()));
					}
					break;
				}
			}

			// Move the ant.
			Node dest = ant.getPath().pop();
			grid.moveAnt(node, dest, ant);
		}
	}

	/**
	 * Finds the closest resource to the node on the grid.
	 * @param grid : the grid.
	 * @param node : the node from where we look around.
	 * @return a node with a resource, or null.
	 */
	private Node findClosestResource(Grid grid, Node node) {
		Node nodeWithResource = null;
		for (Node n : grid.getNeighbors(node)) {
			if (n.getResource() != null && !n.getResource().isEmpty()) {
				nodeWithResource = n;
				break;
			}
		}
		return nodeWithResource;
	}

	/**
	 * Actions triggered when the ant is cropping.
	 * @param ant : the ant cropping.
	 */
	private void crop(Ant ant) {
		// Gets the resource on the node.
		Resource res = ant.getNode().getResource();
		previousNode = ant.getNode();

		if (res != null && res.isEmpty() && ant.getFood() > 0) {
			// If the resource node is empty, and the ant has food, it will return to nest.
			setAntState(AntState.WALKING_NEST);
		} else if (ant.getFood() == ant.getFoodCapacity()) {
			// If the ant cannot carry anymore food, it will return to nest.
			setAntState(AntState.WALKING_NEST);
		} else if (res != null) {
			// Move food from the resource node to the ant.
			ant.addFood(res.consume(ant.getFoodPerBite()));
		} else if (res == null) {
			// If we moved the resource.
			setAntState(AntState.WALKING_NEST);
		}
	}

	/**
	 * Helps to determine whether this resource is interesting or not.
	 * @param ant : the ant (point of view).
	 * @param res : the resource to check.
	 * @return true if the resource is interesting for this ant.
	 */
	private boolean isResourceInteresting(Ant ant, Resource res) {
		return res != null && !res.isEmpty() && res.getAmount() > ant.getFoodCapacity();
	}

	/**
	 * Actions triggered when the ant goes back to its nest.
	 * @param ant : the ant.
	 * @param grid : the grid the ant is on.
	 */
	private void walkToNest(Ant ant, Grid grid) {
		if (ant.getNode().equals(ant.getNest().getNode())) {
			// The ant has arrived to the nest !
			setAntState(AntState.DEPOSE);
			return;
		}
		if (ant.getPath().isEmpty()) {
			// Finds a path to the nest.
			Node src = ant.getNode();
			Node dest = ant.getNest().getNode();
			IPathFinder pf = ant.getNest().getPathFinder();
			Stack<Node> path = pf.getPath(grid, src.getX(), src.getY(), dest.getX(), dest.getY());
			ant.setPath(path);
		}
		// Moves the ant.
		grid.moveAnt(ant.getNode(), ant.getPath().pop(), ant);
		ant.getNode().addPheromones();
	}

	/**
	 * Actions triggered when the ant is following a pheromone trail.
	 * @param ant : the ant moving.
	 * @param grid : the world where the ant is.
	 */
	private void follow(Ant ant, Grid grid) {
		// Grab the node where the ant is, and look for resources.
		Node node = ant.getNode();
		Node nodeWithResource = findClosestResource(grid, node);

		boolean foodNear = nodeWithResource != null && isResourceInteresting(ant, nodeWithResource.getResource());
		boolean foodFound = isResourceInteresting(ant, ant.getNode().getResource()); 

		if (foodFound) {
			// The resource is on the node where the ant is.
			setAntState(AntState.CROP);
			ant.getPath().clear();
		} else if (foodNear) {
			// A resource is close and the ant has to move to another node.
			ant.getPath().clear();
			grid.moveAnt(node, nodeWithResource, ant);
		} else {
			// Looks for all nodes with pheromone around.
			List<Node> pheroNodes = findClosestPheromone(grid, node);
			pheroNodes.remove(previousNode);
			pheroNodes.remove(ant.getNest().getNode());

			// Sorts the nodes, the one with the most pheromones aer listed first.
			Collections.sort(pheroNodes, new PheromoneComparator());

			// Keeps in mind where the ant is from.
			previousNode = node;

			if (pheroNodes.size() == 0) {
				// No pheromones left, let's start exploring again.
				setAntState(AntState.EXPLORING);
			} else {
				// Let's go the node with the biggest amount of pheromones.
				grid.moveAnt(node, pheroNodes.get(0), ant);
			}
		}
	}

	/**
	 * Finds the closest nodes with pheromones.
	 * @param grid : the grid to watch.
	 * @param node : the node from which to look.
	 * @return the closest nodes with pheromones.
	 */
	private List<Node> findClosestPheromone(Grid grid, Node node) {
		List<Node> pheroNodes = new ArrayList<>();
		for (Node neighbor : grid.getNeighbors(node)) {
			if (neighbor.getPheromoneLevel() > 0) {
				pheroNodes.add(neighbor);
			}
		}
		return pheroNodes;
	}

	/** @return a new direction. */
	private Direction findDirection() {
		Direction[] directions;
		int index;
		if (previousDirection == null) {
			// First direction the ant take, totally random.
			directions = Direction.values();
			index = MathUtils.rnd(0, 3);
		} else {
			// Find a random direction on a different axis than the former direction.
			index = MathUtils.rnd(0, 1);
			switch (previousDirection) {
			case NORTH:
			case SOUTH:
				directions = new Direction[] {Direction.WEST, Direction.EAST};
				break;
			case WEST:
			case EAST:
				directions = new Direction[] {Direction.NORTH, Direction.SOUTH};
				break;
			default:
				directions = Direction.values();
				index = MathUtils.rnd(0, 3);
				break;
			}
		}

		return directions[index];
	}

	/**
	 * A direction an ant can take.
	 * @author Vianney DUPOY DE GUITARD
	 */
	private enum Direction {
		NORTH,
		SOUTH,
		WEST,
		EAST;
	}

	/**
	 * Compares two nodes based on their pheromones.
	 * @author Vianney DUPOY DE GUITARD
	 */
	private static class PheromoneComparator implements Comparator<Node> {
		@Override
		public int compare(Node n1, Node n2) {
			return n1.getPheromoneLevel() < n2.getPheromoneLevel() ? 1 : -1;
		}
	}
}